﻿$MsDeployPath="C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe"

function New-MSDeployContentPackage
{
        [cmdletbinding(DefaultParameterSetName="none",SupportsShouldProcess=$True)]
        Param( 
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)] 
        [ValidateScript({Test-Path $_ -PathType Container})] 
        [String] 
        $SourceDirectory,

        [Parameter(Mandatory=$true,ValueFromPipeline=$true)] 
        [ValidateScript({Test-Path $_ -PathType Container})] 
        [String] 
        $DestinationDirectory,
        
        [Parameter(Mandatory=$true)] 
        [String] 
        $PackageName,

        [Parameter(ParameterSetName="DeletePreviousPackage",Mandatory=$true)] 
        [switch] 
        $DeletePreviousPackage,

        [Parameter(ParameterSetName="CompleteExistingPackage",Mandatory=$true)] 
        [switch] 
        $CompleteExistingPackage
		        
        )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start New-MSDeployContentPackage '$PackageName' from '$SourceDirectory' to '$DestinationDirectory'"        
    }
    End
    {
        write-verbose "End New-MSDeployContentPackage '$PackageName' from '$SourceDirectory' to '$DestinationDirectory'"        
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }
    Process
    {                
    
        $source = Resolve-Path -Path $SourceDirectory
        $destination = Resolve-Path -Path $DestinationDirectory

        $PackageFilePath = Join-Path -Path $destination.ProviderPath -ChildPath "$PackageName.zip"
        if ($DeletePreviousPackage -and (Test-Path -Path $PackageFilePath -PathType Leaf))
        {
            Remove-Item -Path $PackageFilePath -Force -ErrorAction Stop
        }

	    Write-verbose "Start creating the expected package '$PackageFilePath'"
        
		#Call msdeploy to synchronize source Diretory and package
		$commandToExecute = '& "C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe" -verb:sync -source:"{0}" -dest:"{1}" -xml'
		$sourceProvider=[System.String]::Format("contentPath='{0}'",$source.ProviderPath)        
		$destinationProvider=[System.String]::Format("package='{0}'",$PackageFilePath)        
		
		$commandToExecute = [System.String]::Format($commandToExecute, $sourceProvider, $destinationProvider)
    
        if ($CompleteExistingPackage)
        {
            $commandToExecute += ' -enableRule:"DoNotDeleteRule"'
        }

		 #Add whatif support
		if($PSBoundParameters['Whatif'])
		{
			$commandToExecute += " -Whatif"
		}			
		Write-verbose "Command to execute : $commandToExecute"
		$outputCommand = Invoke-Command -ScriptBlock ([scriptblock]::create($commandToExecute))
        
        $xmlOutputCommand = [Xml]$outputCommand

        $result = New-Object PSObject -Property @{
                                                  Package=$PackageName
                                                  SourceDirectory=$SourceDirectory
                                                  DestinationDirectory= $DestinationDirectory
                                                  PackageFilePath=$PackageFilePath
                                                  }

        #Add the summary result
        $summary = New-DynamicObject_v2 -Source $xmlOutputCommand.output.syncResults
        $result | Add-Member -MemberType NoteProperty -Name Summary -Value $summary


        #Add the package syncrhonisation detail
        $result | Add-Member -MemberType NoteProperty -Name PackageList -Value @()
        if ($xmlOutputCommand.output.traceEvent)
        {
            $xmlOutputCommand.output.traceEvent | ForEach-Object {
                $packageDetail = New-DynamicObject_v2 -Source $_
                $result.PackageList += $_

            }
        }
        $result
    }
}


function Get-MSDeployContentPackageInfo
{
        [cmdletbinding(SupportsShouldProcess=$True)]
        Param( 
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)] 
        [ValidateScript({Test-Path $_ -PathType Leaf})] 
        [String] 
        $PackageFilePath		        
        )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Get-MSDeployContentPackageInfo for '$PackageFilePath'"        
    }
    End
    {
        write-verbose "End Get-MSDeployContentPackageInfo for '$PackageFilePath'"        
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }
    Process
    {                
    
        $PackageFile = Get-Item -Path $PackageFilePath

	    Write-verbose "Start Getting infor for package '$PackageFilePath'"
        
		#Call msdeploy to synchronize source Diretory and package
		$commandToExecute = '& "C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe" -verb:Dump -source:"{0}" -xml'
		$sourceProvider=[System.String]::Format("package='{0}'",$PackageFilePath)        
		
		$commandToExecute = [System.String]::Format($commandToExecute, $sourceProvider)
    
		Write-verbose "Command to execute : $commandToExecute"
		$outputCommand = Invoke-Command -ScriptBlock ([scriptblock]::create($commandToExecute))
        
        $xmlOutputCommand = [Xml]$outputCommand
        #$xmlOutputCommand

        $result = New-Object PSObject -Property @{
                                                  Package=[System.IO.Path]::GetFileNameWithoutExtension($PackageFile)
                                                  Directory=$PackageFile.Parent.FullName
                                                  Size= $PackageFile.Length
                                                  Sha256=(Microsoft.PowerShell.Utility\Get-FileHash -Path $PackageFilePath -Algorithm SHA256)                                                  
                                                  }

        $Contents = @()
        $result | Add-Member -MemberType NoteProperty -Name Contents -Value $Contents

        foreach($contentInformation in $xmlOutputCommand.output.'MSDeploy.contentPath'.contentPath)
        {
            Expand-MSDeployContentPackageInfo -ParentNode $contentInformation -result $result -parentPath ""
        }

        $result

<#
        #Add the summary result
        $summary = New-DynamicObject_v2 -Source $xmlOutputCommand.output.syncResults
        $result | Add-Member -MemberType NoteProperty -Name Summary -Value $summary


        #Add the package syncrhonisation detail
        $result | Add-Member -MemberType NoteProperty -Name PackageList -Value @()
        if ($xmlOutputCommand.output.traceEvent)
        {
            $xmlOutputCommand.output.traceEvent | ForEach-Object {
                $packageDetail = New-DynamicObject_v2 -Source $_
                $result.PackageList += $_

            }
        }
        $result
        #>
    }
}

function Expand-MSDeployContentPackageInfo
{
        [cmdletbinding(SupportsShouldProcess=$True)]
        Param( 
        [Parameter(Mandatory=$true,ValueFromPipeline=$false)] 
        [System.Xml.XmlElement] 
        $ParentNode,
        
        [Parameter(Mandatory=$true,ValueFromPipeline=$false)] 
        [PSObject]
        $result,
        
        [Parameter(Mandatory=$false,ValueFromPipeline=$false)] 
        [String]
        $parentPath
        		        
        )
    Process
    {
        $currentPath=[System.IO.Path]::Combine($parentPath, $ParentNode.Path)
        if ($ParentNode.Attributes -in ('Directory','Archive'))
        {
            $contentInformation = New-Object PSObject -Property @{
                                                    Path=[System.IO.Path]::Combine($currentPath, $ParentNode.Path)
                                                    Size=$ParentNode.Size
                                                    lastWriteTime=$ParentNode.lastWriteTime
                                                    Type=$ParentNode.Attributes
                                                    }
            $result.Contents += $contentInformation
        }


        if ($ParentNode.dirPath)
        {
            $ParentNode.dirPath | ForEach-Object {
                Expand-MSDeployContentPackageInfo -ParentNode $_ -result $result -parentPath $currentPath    
            }
        }

        if ($ParentNode.filePath)
        {
            $ParentNode.filePath | ForEach-Object {
                Expand-MSDeployContentPackageInfo -ParentNode $_ -result $result -parentPath $currentPath    
            }            
        }

    }
}


function Expand-MSDeployContentPackage
{
        [cmdletbinding(DefaultParameterSetName="none",SupportsShouldProcess=$True)]
        Param( 
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)] 
        [ValidateScript({Test-Path $_ -PathType Leaf})] 
        [String] 
        $PackageFilePath,		        

        [Parameter(Mandatory=$true,ValueFromPipeline=$true)] 
        #[ValidateScript({Test-Path $_ -PathType Container})] 
        [String] 
        $DestinationDirectory,
        
        [Parameter(ParameterSetName="CompleteExistingPackage",Mandatory=$true)] 
        [switch] 
        $CompleteExistingPackage
		        
        )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Expand-MSDeployContentPackage '$PackageFilePath' to '$DestinationDirectory'"        
    }
    End
    {
        write-verbose "End Expand-MSDeployContentPackage '$PackageFilePath' to '$DestinationDirectory'"        
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }
    Process
    {                
    
        $source = Resolve-Path -Path $PackageFilePath
        $destination = Resolve-Path -Path $DestinationDirectory

	    Write-verbose "Start creating the expected package '$PackageFilePath'"
        
		#Call msdeploy to synchronize source Diretory and package
		$commandToExecute = '& "C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe" -verb:sync -source:''{0}'' -dest:''{1}'' -xml'
		$sourceProvider=[System.String]::Format('package="{0}"',$source.ProviderPath)        
		$destinationProvider=[System.String]::Format('contentPath="{0}"',$destination.ProviderPath)        
		
		$commandToExecute = [System.String]::Format($commandToExecute, $sourceProvider, $destinationProvider)
    
        if ($CompleteExistingPackage)
        {
            $commandToExecute += ' -enableRule:"DoNotDeleteRule"'
        }

		 #Add whatif support
		if($PSBoundParameters['Whatif'])
		{
			$commandToExecute += " -Whatif"
		}			
		Write-verbose "Command to execute : $commandToExecute"
		$outputCommand = Invoke-Command -ScriptBlock ([scriptblock]::create($commandToExecute))
        
        $xmlOutputCommand = [Xml]$outputCommand

        $result = New-Object PSObject -Property @{
                                                  DestinationDirectory= $DestinationDirectory
                                                  PackageFilePath=$PackageFilePath
                                                  }

        #Add the summary result
        $summary = New-DynamicObject_v2 -Source $xmlOutputCommand.output.syncResults
        $result | Add-Member -MemberType NoteProperty -Name Summary -Value $summary


        #Add the package syncrhonisation detail
        $result | Add-Member -MemberType NoteProperty -Name PackageList -Value @()
        if ($xmlOutputCommand.output.traceEvent)
        {
            $xmlOutputCommand.output.traceEvent | ForEach-Object {
                $packageDetail = New-DynamicObject_v2 -Source $_
                $result.PackageList += $_

            }
        }
        $result
    }
}


function Assert-MsDeployPresent
{
    Process
    {
        if (-not (Test-Path -Path $MsDeployPath -PathType Leaf))
        {
            throw "MSDeploy V3 is missing exepcted path : '$MsDeployPath'"
        }
    }
}


function Copy-PackageItem
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param
    (
        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [String] 
        $Path,

        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [String] 
        $Destination
    )

    Process
    {
        
        $sourceFile = Resolve-Path -Path $Path
        $sourceFilePath = $sourceFile.ProviderPath


        if (-not (Test-Path -Path $Destination -PathType Container))
        {
            New-Item -Path $Destination -ItemType Directory -Force | out-null
        }

        $sourceHashFile = Get-FileHash -Path $sourceFilePath
        
        Write-Host "Copy $Path on $Destination in progres..." -ForegroundColor Green
        Copy-Item -Path $Path -Destination $Destination -Recurse -Force
        Write-Host "Package $Path has been copied on $Destination"  -ForegroundColor Green
    }
}

function Push-Package_v2
{
        [cmdletbinding(SupportsShouldProcess=$True)]
        Param( 
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)] 
        [ValidateScript({Test-Path $_ -PathType Leaf})] 
        [String] 
        $Path,

        [Parameter(Mandatory=$true,ValueFromPipeline=$true)] 
        [String]
        $Destination,


        [Parameter(Mandatory=$false,ValueFromPipeline=$true)] 
        [System.Management.Automation.Runspaces.PSSession]
        $Session

        )

    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Push-Package $Path to $Destination"
    }
    End
    {
        write-verbose "End Push-Package $Path to $Destination"
        $watch.Stop();
        Write-Verbose "Push-Package duration : $($watch.Elapsed)"
    }
    Process 
    {
        #Test Destination and ensure folder
        if ((Test-Path $Destination -PathType Container) -eq $false)
        {
            Write-Verbose "Destination $Destination doesn't exist, create it"
            New-Item $Destination -ItemType Container | Out-Null
        }

        
        #Get-file to extract the name and create the destinationFile
        $itemSource = Get-Item -Path $Path
        $itemDestinationPath = [System.IO.Path]::Combine($destination, $itemSource.Name)

        #Get source file hash
        $itemSourceHash = Get-FileHash -Path $itemSource.FullName
        
        if (Test-Path -Path $itemDestinationPath -PathType leaf)
        {
            $itemDesinationHash = Get-ManagedFileHash -Path $itemDestinationPath -Session $Session
            if($itemDesinationHash.hash -eq $itemSourceHash.hash)
            {
                write-verbose "Destination item '$itemDestinationPath' is already ok"
                Write-Host "'$Path' has already be copied on '$itemDestinationPath'"
                return
            }
            else
            {
                Remove-Item $itemDestinationPath -Recurse -Force
            }
        }

        Write-Host "Copy $Path on $itemDestinationPath in progres..."
        Copy-Item -Path $Path -Destination $itemDestinationPath -Recurse -Force
        Start-Sleep -Seconds 10
        
        $itemDesinationHash = Get-ManagedFileHash -Path $itemDestinationPath -Session $Session
        if($itemDesinationHash.hash -ne $itemSourceHash.hash)
        {
            throw "Corruption has been detected on file '$itemDestinationPath'"
        }

        Write-Host "Package $Path has been copied on $itemDestinationPath"

    }
}


function Get-ManagedFileHash
{
    [cmdletbinding(SupportsShouldProcess=$True)]
    Param( 
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)] 
        [ValidateScript({Test-Path $_ -PathType Leaf})] 
        [String] 
        $Path,

        [Parameter(Mandatory=$false,ValueFromPipeline=$true)] 
        [System.Management.Automation.Runspaces.PSSession]
        $Session

    )
    Process
    {
        if ($Session)
        {
            Invoke-Command -Session $Session -ScriptBlock {param($path) Get-FileHash -Path $path} -ArgumentList $Path
        }
        else
        {
            Get-FileHash -Path $Path
        }

    }

}


function Sync-MsDeployFolder
{
        [cmdletbinding(DefaultParameterSetName="none",SupportsShouldProcess=$True)]
        Param( 
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)] 
        [ValidateScript({Test-Path $_ -PathType Container})] 
        [String] 
        $SourceDirectory,

        [Parameter(Mandatory=$true,ValueFromPipeline=$true)] 
        [String] 
        $DestinationDirectory,
        
        [Parameter(ParameterSetName="DeletePreviousPackage",Mandatory=$true)] 
        [switch] 
        $DeletePreviousPackage,

        [Parameter(ParameterSetName="CompleteExistingPackage",Mandatory=$true)] 
        [switch] 
        $CompleteExistingPackage
		        
        )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew()
        $eventId = Publish-ToMiddlewareLogs -Log (Get-LogMessage -Command $PSCmdlet.MyInvocation.MyCommand -Message "Start" -AdditionalData $PSBoundParameters)
    }
    End
    {
        $watch.Stop()
        $eventId = Publish-ToMiddlewareLogs -Log (Get-LogMessage -Command $PSCmdlet.MyInvocation.MyCommand -UniqueId $eventId -Message "End" -AdditionalData @{Duration=$watch.Elapsed})
    }
    Process
    {                
    
        $source = Resolve-Path -Path $SourceDirectory

	    Write-verbose "Start Synchronizing from '' the expected package '$PackageFilePath'"
        
		#Call msdeploy to synchronize source Diretory and package
		$commandToExecute = '& "C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe" -verb:sync -source:"{0}" -dest:"{1}" -xml'
		$sourceProvider=[System.String]::Format("contentPath='{0}'",$source.ProviderPath)        
		$destinationProvider=[System.String]::Format("contentPath='{0}'",$DestinationDirectory)        
		
		$commandToExecute = [System.String]::Format($commandToExecute, $sourceProvider, $destinationProvider)
    
        if ($CompleteExistingPackage)
        {
            $commandToExecute += ' -enableRule:"DoNotDeleteRule"'
        }

		 #Add whatif support
		if($PSBoundParameters['Whatif'])
		{
			$commandToExecute += " -Whatif"
		}			
		Write-verbose "Command to execute : $commandToExecute"
		$outputCommand = Invoke-Command -ScriptBlock ([scriptblock]::create($commandToExecute))
        
        $xmlOutputCommand = [Xml]$outputCommand

        $result = New-Object PSObject -Property @{
                                                  SourceDirectory=$SourceDirectory
                                                  DestinationDirectory= $DestinationDirectory
                                                  }

        #Add the summary result
        $summary = New-DynamicObject_v2 -Source $xmlOutputCommand.output.syncResults
        $result | Add-Member -MemberType NoteProperty -Name Summary -Value $summary


        #Add the package syncrhonisation detail
        $result | Add-Member -MemberType NoteProperty -Name PackageList -Value @()
        if ($xmlOutputCommand.output.traceEvent)
        {
            $xmlOutputCommand.output.traceEvent | ForEach-Object {
                $packageDetail = New-DynamicObject_v2 -Source $_
                $result.PackageList += $_

            }
        }
        $result
    }

}